﻿namespace MicroCloud.Systems
{
    /// <summary>
    /// 字典数据存储
    /// </summary>
    public class KeyValueStore : IKeyValueStore
    {
        //字段
        private readonly IServiceProvider _provider;
        private static readonly Random Random = new();

        #region "属性"
        private IRepository<KeyValue, long> KeyValueRepository => _provider.GetRequiredService<IRepository<KeyValue, long>>();
        private IDistributedCache Cache => _provider.GetRequiredService<IDistributedCache>();

        #region "获取 字典数据查询数据集"
        /// <summary>
        /// 获取 字典数据查询数据集
        /// </summary>
        public IQueryable<KeyValue> KeyValues => KeyValueRepository.QueryAsNoTracking();
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个字典数据存储的新实例"
        /// <summary>
        /// 初始化一个字典数据存储 <see cref="KeyValueStore"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public KeyValueStore(IServiceProvider provider)
        {
            _provider = provider;
        }
        #endregion

        #endregion

        #region "方法"
        #region "获取或创建设置信息"
        /// <summary>
        /// 获取或创建设置信息
        /// </summary>
        /// <typeparam name="TSetting">设置类型</typeparam>
        /// <returns>设置实例，数据库中不存在相应节点时返回默认值</returns>
        public TSetting GetSetting<TSetting>() where TSetting : ISetting, new()
        {
            TSetting setting = new();
            Type type = typeof(TSetting);
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(m => m.PropertyType == typeof(IKeyValue)).ToArray();
            foreach (var property in properties)
            {
                var key = ((KeyValue)property.GetValue(setting)).Key;
                var dictionary = GetByKey(key);
                if (dictionary != null)
                {
                    property.SetValue(setting, dictionary);
                }
            }
            return setting;
        }
        #endregion
        #region "保存设置信息"
        /// <summary>
        /// 保存设置信息
        /// </summary>
        /// <param name="setting">设置信息</param>
        public async Task<OperationResult> SaveSetting(ISetting setting)
        {
            Check.NotNull(setting, nameof(setting));

            Type type = setting.GetType();
            var dictionaries = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.PropertyType == typeof(IKeyValue)).Select(p => (IKeyValue)p.GetValue(setting)).ToArray();
            return await CreateOrUpdate(dictionaries);
        }
        #endregion
        #region "获取指定根节点的数据项，如输入“System.User.”，则查找所有键以此开头的项"
        /// <summary>
        /// 获取指定根节点的数据项，如输入“System.User.”，则查找所有键以此开头的项
        /// </summary>
        /// <param name="rootKey">根键路径</param>
        /// <returns>多个数据项</returns>
        public IKeyValue[] GetByRootKey(string rootKey)
        {
            string[] keys = GetKeys(rootKey);
            return keys.Select(key => GetByKey(key)).Where(value => value != null).ToArray();
        }
        #endregion
        #region "获取指定键名的数据项，先从缓存中获取，如果不存在，再从数据库查找并缓存"
        /// <summary>
        /// 获取指定键名的数据项，先从缓存中获取，如果不存在，再从数据库查找并缓存
        /// </summary>
        /// <param name="key">键名</param>
        /// <returns>数据项</returns>
        public IKeyValue GetByKey(string key)
        {
            Check.NotNullOrEmpty(key, nameof(key));

            // 有效期为 7 ± 1 天，防止缓存集中过期
            int seconds = 7 * 24 * 3600 + Random.Next(-24 * 3600, 24 * 3600);
            string cacheKey = GetCacheKey(key);
            var value = Cache.Get(cacheKey, () => KeyValueRepository.QueryAsNoTracking(m => m.Key == key, false).FirstOrDefault(), seconds);
            return value;
        }
        #endregion
        #region "检查字典数据是否存在"
        /// <summary>
        /// 检查字典数据是否存在
        /// </summary>
        /// <param name="predicate">检查谓语表达式</param>
        /// <param name="id">字典数据编号</param>
        /// <returns>字典数据是否存在</returns>
        public async Task<bool> CheckExists(Expression<Func<KeyValue, bool>> predicate, long id = default)
        {
            return await KeyValueRepository.CheckExistsAsync(predicate, id);
        }
        #endregion
        #region "添加或更新字典数据"
        /// <summary>
        /// 添加或更新字典数据
        /// </summary>
        /// <param name="key">数据键名</param>
        /// <param name="value">数据值</param>
        /// <param name="display">显示名称</param>
        /// <param name="remark">备注</param>
        /// <param name="isLocked">是否锁定</param>
        /// <returns>业务操作结果</returns>
        public async Task<OperationResult> CreateOrUpdate(string key, object value, string display = "", string remark = "", bool isLocked = false)
        {
            Check.NotNullOrEmpty(key, nameof(key));

            var dictionary = new KeyValue(key, value, display, remark, isLocked);
            return await CreateOrUpdate(dictionary);
        }
        #endregion
        #region "添加或更新字典数据"
        /// <summary>
        /// 添加或更新字典数据
        /// </summary>
        /// <param name="dtos">字典数据DTO</param>
        /// <returns>业务操作结果</returns>
        public async Task<OperationResult> CreateOrUpdate(params IKeyValue[] dtos)
        {
            Check.NotNull(dtos, nameof(dtos));

            if (dtos.Length > 0)
            {
                var values = dtos.OfType<KeyValue>().ToArray();
                List<string> removeKeys = new();
                IUnitOfWork unitOfWork = _provider.GetUnitOfWork(true);
                foreach (var dto in values)
                {
                    var pair = KeyValues.FirstOrDefault(m => m.Key == dto.Key);
                    if (pair == null)
                    {
                        pair = dto;                  
                        KeyValueRepository.Insert(pair);
                    }
                    else if (pair.Value != dto.Value)
                    {
                        pair.Value = dto.Value;
                        pair.Display = dto.Display;
                        pair.Remark = dto.Remark;
                        pair.OrderNo = dto.OrderNo;
                        pair.IsLocked = dto.IsLocked;
                        var result = KeyValueRepository.Update(pair);
                        removeKeys.AddIf(pair.Key, result.Succeeded);
                    }
                }
                await unitOfWork.CommitAsync();

                string[] cacheKeys = removeKeys.Select(m => GetCacheKey(m)).ToArray();
                await Cache.RemoveAsync(cacheKeys);
            }

            return OperationResult.Success();
        }
        #endregion
        #region "删除字典数据"
        /// <summary>
        /// 删除字典数据
        /// </summary>
        /// <param name="ids">字典数据编号</param>
        /// <returns>业务操作结果</returns>
        public async Task<OperationResult> Delete(params long[] ids)
        {
            Check.NotNull(ids, nameof(ids));

            if (ids.Length > 0)
            {
                List<string> removeKeys = new();
                IUnitOfWork unitOfWork = _provider.GetUnitOfWork(true);
                foreach (var id in ids)
                {
                    var pair = await KeyValueRepository.GetAsync(id);
                    if (pair == null)
                    {
                        continue;
                    }

                    var result = await KeyValueRepository.DeleteAsync(pair);
                    removeKeys.AddIf(pair.Key, result.Succeeded);
                }
                await unitOfWork.CommitAsync();

                string[] cacheKeys = removeKeys.Select(m => GetCacheKey(m)).ToArray();
                await Cache.RemoveAsync(cacheKeys);
            }

            return OperationResult.Success();
        }
        #endregion
        #region "删除以根键路径为起始的所有字典项，如输入“System.User.”，所有键以此开头的项都会被删除"
        /// <summary>
        /// 删除以根键路径为起始的所有字典项，如输入“System.User.”，所有键以此开头的项都会被删除
        /// </summary>
        /// <param name="rootKey">根键路径</param>
        /// <returns>业务操作结果</returns>
        public async Task<OperationResult> DeleteByRootKey(string rootKey)
        {
            //var ids = DictionaryRepository.QueryAsNoTracking(m => m.Key.StartsWith(rootKey)).Select(m => m.Id).ToArray();
            var ids = KeyValueRepository.QueryAsNoTracking(m => KeyValueRepository.Like(m.Key, $"{rootKey}%", @"\")).Select(m => m.Id).ToArray();
            return await Delete(ids);
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "获取键集合"
        /// <summary>
        /// 获取键集合
        /// </summary>
        /// <param name="rootKey"></param>
        /// <returns></returns>
        private string[] GetKeys(string rootKey)
        {
            //var keys = DictionaryRepository.QueryAsNoTracking(m => m.Key.StartsWith(rootKey)).Select(m => m.Key).Distinct().OrderBy(m => m).ToArray();
            var keys = KeyValueRepository.QueryAsNoTracking(m => KeyValueRepository.Like(m.Key, $"{rootKey}%", @"\")).Select(m => m.Key).Distinct().OrderBy(m => m).ToArray();
            return keys;
        }
        #endregion
        #region "获取缓存键"
        /// <summary>
        /// 获取缓存键
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string GetCacheKey(string key)
        {
            return $"Systems:Dictionary:{key}";
        }
        #endregion

        #endregion

    }

}
